#include "mock.h"
#include "common.h"
#include "init.h"
#include "fifo.h"
#include <linux/timer.h>
#include <linux/atomic.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/version.h>

// kfifo to send sync_counter_frame
static struct kfifo *fifo = NULL;

static const char *mock_serial = NULL;

static struct timer_list timer;

// we use a char device to mock the pedal
// open char device will start trigger
static struct cdev pedal;

static dev_t dev_no;

static ssize_t cdev_read(struct file* fp, char __user *, size_t, loff_t *);
static ssize_t cdev_write(struct file* fp, const char __user *, size_t, loff_t *);
static int cdev_open(struct inode *inode, struct file *fp);
static int cdev_release(struct inode *inode, struct file *fp);

static struct file_operations fops = {
	.read = cdev_read,
	.write = cdev_write,
	.open = cdev_open,
	.release = cdev_release,
};

// device class and device
static struct class *pedal_class = NULL;
static struct device *device = NULL;

static int init_mock_trigger(struct init_block *block);
static void cleanup_mock_trigger(struct init_block *block);

static struct init_block mock_trigger_init = {
	.name = "mock trigger",
	.init_fn = init_mock_trigger,
	.cleanup_fn = cleanup_mock_trigger,
	.drop_fn = NULL,
};

static atomic_t trigger_flag = ATOMIC_INIT(0);
static atomic_t pedal_flag = ATOMIC_INIT(0);
static atomic_t sync_counter = ATOMIC_INIT(0);

static void timer_callback(struct timer_list *timer) {
	int ret = 0;
	struct sync_counter_frame frame;
	int flag = atomic_read_acquire(&trigger_flag);

	// PR_INFO("mock timer triggered");
	if (flag) {
		frame.time_ns = ktime_get_boottime_ns();
		frame.count = atomic_inc_return(&sync_counter) - 1;
		// PR_INFO("mock write frame to fifo");
		ret = kfifo_in(fifo, &frame, sizeof(struct sync_counter_frame));
		if (ret == 0) {
			PR_ERR("mock write kfifo failed, kfifo full");
		}
	} else {
		ret = atomic_xchg(&sync_counter, 0);
		if (ret != 0) {
			PR_INFO("pedal is released");
		}
	}
	mod_timer(timer, jiffies + HZ / MOCK_SYNC_FREQ);
}

static int init_mock_trigger(struct init_block *block) {
	int ret = 0;
	// setup timer with callback
	timer_setup(&timer, timer_callback, 0);

	// register
	ret = alloc_chrdev_region(&dev_no, 0, 1, "mock-pedal");
	if (ret) {
		PR_ERR("alloc char dev region failed");
		return ret;
	}
	PR_INFO("pedal MAJOR number is %d", MAJOR(dev_no));
	PR_INFO("pedal MINOR number is %d", MINOR(dev_no));

	// setup char device
	cdev_init(&pedal, &fops);
	pedal.owner = THIS_MODULE;

	PR_INFO("add pedal char device");
	ret = cdev_add(&pedal, dev_no, 1);
	if (ret) {
		PR_ERR("cdev add failed");
		return ret;
	}

	PR_INFO("create pedal class");

#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
	pedal_class = class_create("pedal");
#else
	pedal_class = class_create(THIS_MODULE, "pedal");
#endif

	if (IS_ERR(pedal_class)) {
		PR_ERR("create pedal class failed");
		ret = PTR_ERR(pedal_class);
		return ret;
	}

	PR_INFO("create pedal device");
	device = device_create(pedal_class, NULL, dev_no, NULL, "mock-pedal");
	if (device == NULL) {
		PR_ERR("create pedal device failed");
		return -EBUSY;
	}

	return ret;
}


static void cleanup_mock_trigger(struct init_block *block) {
	del_timer_sync(&timer);

	if (pedal.owner != NULL) {

		if (device != NULL) {
			PR_INFO("cleanup pedal device");
			device_destroy(pedal_class, dev_no);
			PR_INFO("finish cleanup pedal device");
		}

		if (pedal_class != NULL) {
			PR_INFO("cleanup pedal class");
			class_destroy(pedal_class);
			PR_INFO("finish cleanup pedal class");
		}

		cdev_del(&pedal);
		// cleanup char device
		unregister_chrdev_region(dev_no, 1);
	}
	return;
}

ssize_t cdev_read(struct file* fp, char __user *buf, size_t size, loff_t *offset) { 
	return 0;
}


ssize_t cdev_write(struct file* fp, const char __user *buf, size_t size, loff_t *offset)
{
	return -EINVAL;
}


int cdev_open(struct inode *inode, struct file *fp)
{
	int ret = atomic_xchg(&pedal_flag, 1);
	struct sync_counter_frame frame;
	if (ret == 1) {
		return -EINVAL;
	}

	if (!try_module_get(THIS_MODULE)) {
		return -EINVAL;
	};

	frame.time_ns = ktime_get_boottime_ns();
	frame.count = TRIGGER_COUNT;

	atomic_xchg(&trigger_flag, 1);

	ret = kfifo_in(fifo, &frame, sizeof(struct sync_counter_frame));
	if (ret != sizeof(struct sync_counter_frame)) {
		PR_ERR("send sync_counter trigger frame to kfifo failed");
	}

	return 0;
}


int cdev_release(struct inode *inode, struct file *fp)
{
	int ret = atomic_xchg(&pedal_flag, 0);
	if (ret == 0) {
		// cdev already closed
		return -EINVAL;
	}
	atomic_xchg(&trigger_flag, 0);
	module_put(THIS_MODULE);
	return 0;
}

int setup_mock_trigger(struct kfifo *_fifo)
{
	fifo = _fifo;
	return try_init(&mock_trigger_init);
}

void start_mock_trigger(void)
{
	PR_INFO("start mock trigger");
	mod_timer(&timer, jiffies + HZ / MOCK_SYNC_FREQ);
}

struct mock_source_cfg {
	struct init_block init;
	struct timer_list timer;
	struct kfifo *fifo;
	atomic_t counter;
	atomic_t enable;
};

static struct mock_source_cfg source_cfg = {
	.enable = ATOMIC_INIT(0),
	.counter = ATOMIC_INIT(0),
};

static void mock_source_timer_callback(struct timer_list *timer);

static int init_mock_source(struct init_block *init)
{
	int ret = 0;
	struct mock_source_cfg *cfg = container_of(init, struct mock_source_cfg, init);
	timer_setup(&cfg->timer, mock_source_timer_callback, 0);
	mod_timer(&cfg->timer, jiffies + HZ / MOCK_SYNC_FREQ);
	return ret;
}

static void cleanup_mock_source(struct init_block *init)
{
	struct mock_source_cfg *cfg = container_of(init, struct mock_source_cfg, init);
	del_timer_sync(&cfg->timer);
	return;

}

static void mock_source_timer_callback(struct timer_list *timer)
{
	int ret = 0;
	struct sync_counter_frame frame;
	int flag = atomic_read_acquire(&source_cfg.enable);

	// PR_INFO("mock timer triggered");
	if (flag) {
		PR_INFO("mock source tick, flag %d", flag);
		frame.time_ns = ktime_get_boottime_ns();
		frame.count = atomic_inc_return(&sync_counter) - 1;

		// PR_INFO("mock write frame to fifo");
		ret = kfifo_in(source_cfg.fifo, &frame, sizeof(struct sync_counter_frame));
		if (ret == 0) {
			PR_ERR("mock source write kfifo failed, kfifo full");
		}
	} else {
		ret = atomic_xchg(&source_cfg.counter, 0);
		if (ret != 0) {
			PR_INFO("mock source is stopped");
		}
	}
	mod_timer(timer, jiffies + HZ / MOCK_SYNC_FREQ);
}

int setup_mock_source(struct kfifo *fifo, const char *_mock_serial)
{
	mock_serial = _mock_serial;
	source_cfg.init.init_fn = init_mock_source;
	source_cfg.init.cleanup_fn = cleanup_mock_source;
	source_cfg.init.drop_fn = NULL;
	source_cfg.fifo = fifo;
	return try_init(&source_cfg.init);
}

void start_mock_source(void)
{
	struct file *filp = NULL;
	PR_INFO("enable mock source");
	
	if (mock_serial != NULL) {
		filp = filp_open(mock_serial, O_WRONLY, 0666);
		if (IS_ERR_OR_NULL(filp)) {
			PR_ERR("open mock serial failed");
		} else {
			PR_INFO("open mock serial success");
			kernel_write(filp, "DOWN", 4, &filp->f_pos);
			PR_INFO("send \"DOWN\" to serial");
			filp_close(filp, current->files);
		}
	}
	atomic_set_release(&source_cfg.enable, 1);
}

void test_mock_source(void)
{
	struct file *filp = NULL;
	PR_INFO("enable mock source");
	
	if (mock_serial != NULL) {
		filp = filp_open(mock_serial, O_WRONLY, 0666);
		if (IS_ERR_OR_NULL(filp)) {
			PR_ERR("open mock serial failed");
		} else {
			PR_INFO("open mock serial success");
			kernel_write(filp, "DOWN", 4, &filp->f_pos);
			PR_INFO("send \"DOWN\" to serial");
			filp_close(filp, current->files);
		}
	}
	atomic_set_release(&source_cfg.enable, 1);
}

void stop_mock_source(void)
{

	struct file *filp = NULL;
	PR_INFO("disable mock source");
	if (mock_serial != NULL) {
		filp = filp_open(mock_serial, O_WRONLY, 0666);
		if (IS_ERR_OR_NULL(filp)) {
			PR_ERR("open mock serial failed");
		} else {
			PR_INFO("open mock serial success");
			kernel_write(filp, "UPUP", 4, &filp->f_pos);
			PR_INFO("send \"UPUP\" to serial");
			filp_close(filp, current->files);
		}
	}
	atomic_set_release(&source_cfg.enable, 0);
}

